package org.summerframework.component.security.session.service.impl;

import org.summerframework.component.security.Constants;
import org.summerframework.component.security.LoginData;
import org.summerframework.component.security.context.RequestContext;
import org.summerframework.component.security.controller.BaseController;
import org.summerframework.component.security.core.config.LoginProperties;
import org.summerframework.component.security.core.service.LoginManage;
import org.summerframework.component.security.core.service.LoginService;
import org.summerframework.component.security.core.service.LogoutService;
import org.summerframework.component.security.core.service.ThirdPartyLoginService;
import org.summerframework.component.security.enumeration.LoginTypeEnum;
import org.summerframework.component.security.session.config.SessionIdAutoConfiguration;
import org.summerframework.component.security.core.SecurityResultCode;
import org.summerframework.core.base.result.DefaultResultCode;
import org.summerframework.core.base.result.ResultDTO;
import org.summerframework.core.exception.AppException;
import org.summerframework.core.exception.WebException;
import org.summerframework.core.util.CheckUtil;
import org.summerframework.core.util.SpringContextUtil;
import org.summerframework.core.util.UrlEncodeUtil;
import org.summerframework.core.util.UrlUtil;
import org.summerframework.web.view.DefaultMappingJackson2JsonView;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.stereotype.Service;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;

/**
 * {@link LoginManage}
 *
 * @author 石超
 * @version v1.0.0
 */
@Slf4j
@Service
public class LoginManageSupper implements LoginManage {

    @Resource
    private LoginProperties loginProperties;

    @Autowired(required = false)
    private List<LoginService> loginServices;

    @Autowired
    private ApplicationContext applicationContext;

    @Resource
    private SessionIdAutoConfiguration.HeaderHttpSessionIdProperty headerHttpSessionIdProperty;

    @Resource
    private LogoutService logoutService;

    private Map<Integer, LoginService> cacheLoginService = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        if (loginServices == null) {
            return;
        }
        AnnotationAwareOrderComparator.sort(loginServices);
    }

    @Override
    public <T> ModelAndView login(RequestContext<LoginData<T>> context) {
        return login(context, null);
    }

    @Override
    public <T> ModelAndView login(RequestContext<LoginData<T>> context, Integer loginType) {
        LoginService loginService = null;
        boolean isBind = ServletRequestUtils.getBooleanParameter(context.getRequest(), "isBind", false);

        if (loginType == null) {
            // 3.登录类型
            loginType = ServletRequestUtils.getIntParameter(context.getRequest(), Constants.LOGIN_TYPE, LoginTypeEnum.NORMAL_FLOW.getType());
        }

        try {
            if (LoginTypeEnum.ERROR_FLOW.isType(loginType)) {
                throw new WebException(DefaultResultCode.ILLEGAL_ARGUMENT);
            }

            //页面跳转
            if (LoginTypeEnum.NORMAL_FLOW.isType(loginType)) {
                context.getLoginData().setJson(0);
                return loginUrl(ServletRequestUtils.getStringParameter(context.getRequest(), Constants.LOGIN_STYPE, "default"));
            }

            LoginService.CURR_LOGIN_DATA.get().setLoginType(loginType);
            loginService = getLoginService(loginType);

            if (loginService == null) {
                throw new WebException(DefaultResultCode.ILLEGAL_ARGUMENT, new IllegalArgumentException("没有对应的处理器:" + loginType));
            }

            if (LoginTypeEnum.QRCODE_FLOW.isType(loginType) && context.getLoginData().isLogin() && !isBind) {
                context.getLoginData().setSessionId(context.getSession().getId());
                logoutService.logout(context.getLoginData());
            }

            LoginData loginData = loginService.setLoginContext();
            loginService.finishLogin();

            LoginData finshLoginData = context.getLoginData();

            loginData.setSessionKey((String) context.getSession().getAttribute(Constants.SESSION_WECHAR_SESSION_KEY));

            finshLoginData.setSessionId(context.getSession().getId());
            finshLoginData.setSessionKey((String) context.getSession().getAttribute(Constants.SESSION_WECHAR_SESSION_KEY));

            // 登录成功
            if (LoginTypeEnum.LOGOUT.isType(loginType)) {
                context.getSession().invalidate();
                if (loginData.isJson()) {
                    ModelAndView modelAndView = new ModelAndView();
                    modelAndView.setView(SpringContextUtil.getBean(DefaultMappingJackson2JsonView.class));
                    modelAndView.addObject(ResultDTO.ok());
                    return modelAndView;
                } else {
                    return loginUrl(ServletRequestUtils.getStringParameter(context.getRequest(), Constants.LOGIN_STYPE, "default"));
                }
            } else if (!loginData.isJson()) {
                Integer finalLoginType = loginType;
                return modelAndView(loginProperties.getPageResult(), loginData.getStyle(), UrlEncodeUtil.decodeURL(loginData.getRedirectUrl()), (style, flag) -> {
                    ModelAndView modelAndView1 = new ModelAndView();

                    if (flag) {
                        modelAndView1.setViewName(style);
                        modelAndView1.addObject(BaseController.RESULT_VAR, ResultDTO.ok(finshLoginData));
                        modelAndView1.addObject(BaseController.REQUEST_CONTEXT, context);
                        modelAndView1.addObject(BaseController.URL_UTIL, UrlUtil.getUrl());
                        String redirectUrl = UrlEncodeUtil.decodeURL(loginData.getRedirectUrl());

                        if (headerHttpSessionIdProperty.getName() != null && LoginTypeEnum.LOGOUT.isType(finalLoginType)) {
                            boolean isParam = redirectUrl.contains("?");

                            if (isParam) {
                                redirectUrl += "&";
                            } else {
                                redirectUrl += "?";
                            }

                            redirectUrl += headerHttpSessionIdProperty.getName() + "=" + finshLoginData.getSessionId();
                        }
                        modelAndView1.addObject("redirectUrl", redirectUrl);
                    } else {
                        modelAndView1.setView(new RedirectView(style));

                        if (headerHttpSessionIdProperty.getName() != null && LoginTypeEnum.LOGOUT.isType(finshLoginData.getLoginType())) {
                            modelAndView1.addObject(headerHttpSessionIdProperty.getName(), finshLoginData.getSessionId());
                        }
                    }

                    return modelAndView1;
                });
            } else {
                return modelAndView(loginProperties.getJsonResult(), loginData.getStyle(), "jsonView", (style, flag) -> {
                    ModelAndView modelAndView1 = new ModelAndView();

                    if (flag) {
                        modelAndView1.setViewName(style);
                        modelAndView1.addObject(BaseController.RESULT_VAR, ResultDTO.ok(finshLoginData));
                        modelAndView1.addObject(BaseController.REQUEST_CONTEXT, context);
                        modelAndView1.addObject(BaseController.URL_UTIL, UrlUtil.getUrl());
                    } else {
                        modelAndView1.setView(SpringContextUtil.getBean(DefaultMappingJackson2JsonView.class));
                        modelAndView1.addObject(ResultDTO.ok(finshLoginData));
                    }


                    return modelAndView1;
                });
            }
        } catch (Exception e) {
            AppException appException = e instanceof AppException ? (AppException) e : new WebException(DefaultResultCode.SYSTEM_ERROR, e);
            LoginData loginData = LoginService.CURR_LOGIN_DATA.get();
            String redirectUrl = loginData.getRedirectUrl();
            TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

            context.setException(appException);
            log.error("登录异常啦", e);

            if (LoginTypeEnum.QRCODE_FLOW.isType(loginType) && !isBind) {
                context.getLoginData().setUser(null);
                if (loginService != null) {
                    loginService.updateFailCount(false);
                }

                LoginService logoutService = getLoginService(LoginTypeEnum.LOGOUT);

                if (logoutService != null) {
                    logoutService.finishLogin();
                }
            }

            if (!loginData.isJson()) {
                if (loginService instanceof ThirdPartyLoginService) {
                    final ModelAndView modelAndView = new ModelAndView(new RedirectView(redirectUrl));
                    modelAndView.addObject("message", appException.getResultDTO().getErrMsg());
                    return modelAndView;
                } else {
                    return modelAndView(loginProperties.getPageResult(), loginData.getStyle(), loginProperties.getLoginUrl(), (style, flag) -> {
                        ModelAndView modelAndView1 = new ModelAndView();

                        if (flag) {
                            modelAndView1.setViewName(style);
                            modelAndView1.addObject(BaseController.RESULT_VAR, appException.getResultDTO());
                            modelAndView1.addObject(BaseController.REQUEST_CONTEXT, context);
                            modelAndView1.addObject(BaseController.URL_UTIL, UrlUtil.getUrl());
                            modelAndView1.addObject(BaseController.ERROR_URL, loginProperties.getLoginUrl());
                            modelAndView1.addObject(BaseController.ERROR_BUTTON, "返回登录");
                        } else {
                            RedirectView redirectView = new RedirectView(style);
                            redirectView.setExposeModelAttributes(true);
                            log.debug("{}", redirectView);
                            log.debug(style);
                            modelAndView1.setView(redirectView);
                        }

                        modelAndView1.addObject("message", appException.getResultDTO().getErrMsg());
                        modelAndView1.addObject("redirectURL", UrlEncodeUtil.encodeURL(redirectUrl));

                        return modelAndView1;
                    });
                }
            } else {
                return modelAndView(loginProperties.getJsonResult(), loginData.getStyle(), "jsonView", (style, flag) -> {
                    ModelAndView modelAndView1 = new ModelAndView();
                    modelAndView1.addObject(BaseController.RESULT_VAR, appException.getResultDTO());

                    if (flag) {
                        modelAndView1.setViewName(style);
                        modelAndView1.addObject(BaseController.REQUEST_CONTEXT, context);
                        modelAndView1.addObject(BaseController.URL_UTIL, UrlUtil.getUrl());
                        modelAndView1.addObject(BaseController.ERROR_URL, loginProperties.getLoginUrl());
                    } else {
                        modelAndView1.setView(SpringContextUtil.getBean(DefaultMappingJackson2JsonView.class));
                    }


                    return modelAndView1;
                });
            }
        } finally {
            LoginService.CURR_LOGIN_DATA.remove();
        }
    }

    private ModelAndView loginUrl(String style) {
        return modelAndView(loginProperties.getView(), style, "new_login", (value, flag) -> {
            ModelAndView modelAndView = new ModelAndView();

            if (CheckUtil.isUrl(loginProperties.getLoginUrl())) {
                modelAndView.setViewName(value);
            } else if (CheckUtil.isUrl(value)) {
                modelAndView.setView(new RedirectView(value));
            } else {
                modelAndView.setViewName(value);
            }

            return modelAndView;
        });
    }

    private ModelAndView modelAndView(Map<String, String> map, String style, String defaultValue, BiFunction<String, Boolean, ModelAndView> biFunction) {
        style = map.get(style);

        return biFunction.apply(style == null ? defaultValue : style, style != null);
    }

    @Override
    public LoginService getLoginService(Integer loginTypeEnum) {
        LoginService cacheLoginService = this.cacheLoginService.get(loginTypeEnum);

        if (cacheLoginService != null) {
            return cacheLoginService;
        }

        for (LoginService loginService : loginServices) {
            for (Integer type : loginService.hasParseLoginType()) {
                if (type.equals(loginTypeEnum)) {
                    this.cacheLoginService.put(loginTypeEnum, loginService);
                    return loginService;
                }
            }
        }

        throw new WebException(SecurityResultCode.ERROR_NOT_LOGIN_SERVER);
    }

    public LoginService getLoginService(LoginTypeEnum loginTypeEnum) {
        return getLoginService(loginTypeEnum.getType());
    }
}
